j8.prototype.initScanner = function() {
  var that = this;

  /* CharacterStream */
  this.CharacterStream = function(str) {
    this.str = str;
    this.position = 0;
  };

  this.CharacterStream.prototype.hasMore = function() { return this.position < this.str.length; };
  this.CharacterStream.prototype.getNext = function() { return this.str[this.position++]; };
  this.CharacterStream.prototype.seek = function(position) { this.position = position; };


  /* ScannerBuffer */
  this.ScannerBuffer = function() {
    this.pushBackBuffer = [];
  };

  this.ScannerBuffer.prototype.initialize = function(stream) {
    this.stream = stream;
    this.position = 0;
  };

  this.ScannerBuffer.prototype.pushBack = function(ch) {
    this.pushBackBuffer.push(this.last);
    this.last = ch;
    this.position--;
  };

  // returns a value < 0 when the buffer end is reached
  this.ScannerBuffer.prototype.advance = function() {
    // NOTE: It is of importance to Persian / Farsi resources that we do
    // *not* strip format control characters in the scanner; see
    //
    //    https://bugzilla.mozilla.org/show_bug.cgi?id=274152
    //
    // So, even though ECMA-262, section 7.1, page 11, dictates that we
    // must remove Unicode format-control characters, we do not. This is
    // in line with how IE and SpiderMonkey handles it.
    if (this.pushBackBuffer.length > 0) {
      this.position++;
      return this.last = this.pushBackBuffer.pop(); 
    } else if (this.stream.hasMore()) {
      this.position++;
      return this.last = this.stream.getNext();
    } else {
      // note: currently the following increment is necessary to avoid a
      // test-parser problem!
      this.position++;
      return this.last = -1;
    }
  };

  this.ScannerBuffer.prototype.seekForward = function(position) {
    this.position = position;
    this.stream.seek(position);
    //NOTE: looks like we need to discard pushBackBuffer at this moment.
  };


  /* LiteralBuffer */
  this.LiteralBuffer = function() {

  };

  this.LiteralBuffer.prototype.reset = function() { this.position = 0; };
  this.LiteralBuffer.prototype.initialize = function(str) {
    this.data = [];
    // TODO: use jQuery's each.
    for (var i = 0; i < str.length; i++) {
      this.data.push(str[i]);
    }
    this.reset();
  };
  this.LiteralBuffer.prototype.addChar = function(ch) {
    this.data[this.position++] = ch;
  };


  /* Scanner */
  this.Scanner = function() {
    this.stream = new that.ScannerBuffer();
    this.literals = new that.LiteralBuffer();
    this.literals.initialize("");
    this.hasLineTerminatorBeforeNext = false;
  };

  this.Scanner.prototype.init = function(stream, position) {
    // Initialize the source buffer.
    this.stream.initialize(stream);
    this.position = position;

    // Reset literals buffer
    this.literals.reset();

    this.current = new Object();
    this.next = new Object();    

    // Set c0_ (one character ahead)
    this.advance();

    // Skip initial whitespace (allowing HTML comment ends) and scan
    // first token.
    this.skipWhiteSpace(true);
    this.scan();
  };

  this.Scanner.prototype.peek = function() { return this.next.token; };
  this.Scanner.prototype.getLocation = function () { return this.current.location; };
  this.Scanner.prototype.peekLocation = function() { return this.next.location; };

  // Returns the literal string, if any, for the current token (the
  // token returned by Next()).
  this.Scanner.prototype.getLiteralString = function() {
    return this.literals.data.slice(this.current.literalPosition, this.current.literalEnd).join("");
  };

  // Returns the literal string for the next token (the token that
  // would be returned if Next() were called).
  this.Scanner.prototype.getNextLiteralString = function() {
    return this.literals.data.slice(this.next.literalPosition, this.next.literalEnd).join("");
  };

  // Scans the input as a regular expression pattern, previous
  // character(s) must be /(=). Returns true if a pattern is scanned.
  this.Scanner.prototype.scanRegExpPattern = function(seenEqual) {
    // TODO: implement this method.
  };

  // Returns true if regexp flags are scanned (always since flags can
  // be empty).
  this.Scanner.prototype.scanRegExpFlags = function() {
    // TODO: implement this method.
  };

  this.Scanner.prototype.getNext = function() {
    this.current = this.next;
    this.next = new Object();
    this.scan();
    return this.current.token;
  };

  this.Scanner.prototype.scan = function() {
    var hasLineTerminator = false;
    var start = -1;
    var token = null;
    do {
      this.skipWhiteSpace(hasLineTerminator);
      hasLineTerminator = this.hasLineTerminatorBeforeNext;
      start = this.stream.position;
      token = this.scanToken();
    } while (token.index == that.$TI("COMMENT"));
    this.next.token = token;
    this.next.location = { "start": start, "end": this.stream.position };    
  };

  this.Scanner.prototype.skipWhiteSpace = function(initial) {
    this.hasLineTerminatorBeforeNext = initial;
    while (true) {
      while (this.isWhiteSpace(this.c0)) {
        // isWhiteSpace() includes line terminators!
        if (this.isLineTerminator(this.c0)) {
          // Ignore line terminators, but remember them. This is necessary
          // for automatic semicolon insertion.
          this.hasLineTerminatorBeforeNext = true;
        }
        this.advance();
      }

      // If there is an HTML comment end '-->' at the beginning of a
      // line (with only whitespace in front of it), we treat the rest
      // of the line as a comment. This is in line with the way
      // SpiderMonkey handles it.
      if (this.c0 == '-' && this.hasLineTerminatorBeforeNext) {
        this.advance();
        if (this.c0 == '-') {
          this.advance();
          if (this.c0 == '>') {
            // Treat the rest of the line as a comment.
            this.skipSingleLineComment();
            // Continue skipping white space after the comment.
            continue;
          }
          this.pushBack('-');
        }
        this.pushBack('-');
      }
      return;
    }
  };

  this.Scanner.prototype.isWhiteSpace = function(ch) {
    // TODO: add other space characters here.
    return (ch == " " || ch == "\r" || ch == "\n" || ch == "\t");
  };

  this.Scanner.prototype.isLineTerminator = function(ch) {
    return (ch == "\r" || ch == "\n");
  };

  this.Scanner.prototype.isDecimalDigit = function(ch) {
    return ch >= "0" && ch <= "9";
  };

  this.Scanner.prototype.isConnectorPunctuation = function(ch) {
    switch (ch) {
      case "\u005F":
      case "\u203F":
      case "\u2040":
      case "\u2054":
      case "\uFE33":
      case "\uFE34":
      case "\uFE4D":
      case "\uFE4E":
      case "\uFE4F":
      case "\uFF3F": return true;
      default: return false;
    }
  };

  this.Scanner.prototype.skipSingleLineComment = function() {
    this.advance();

    // The line terminator at the end of the line is not considered
    // to be part of the single-line comment; it is recognized
    // separately by the lexical grammar and becomes part of the
    // stream of input elements for the syntactic grammar (see
    // ECMA-262, section 7.4, page 12).
    while ( this.c0 != -1 && !this.isLineTerminator(this.c0)) {
      this.advance();
    }
    return that.$T("COMMENT");
  };

  this.Scanner.prototype.skipMultiLineComment = function() {
    this.advance();
    while (this.c0 != -1) {
      var ch = this.c0;
      this.advance();
      // If we have reached the end of the multi-line comment, we
      // consume the '/' and insert a whitespace. This way all
      // multi-line comments are treated as whitespace - even the ones
      // containing line terminators. This contradicts ECMA-262, section
      // 7.4, page 12, that says that multi-line comments containing
      // line terminators should be treated as a line terminator, but it
      // matches the behaviour of SpiderMonkey and KJS.
      if (ch == "*" && this.c0 == "/") {
        this.c0 = " ";
        return that.$T("COMMENT");
      }
    }
    // Unterminated multi-line comment.
    return that.$T("ILLEGAL");
  };

  this.Scanner.prototype.scanHtmlComment = function() {
    this.advance();
    if (this.c0 == "-") {
      this.advance();
      if (this.c0 == "-") { return this.skipSingleLineComment(); }
      this.pushBack("-");
    }
    this.pushBack("!");
    return that.$T("LT");
  };

  this.Scanner.prototype.startLiteral = function() {
    this.next.literalPosition = this.literals.position;
  };

  this.Scanner.prototype.terminateLiteral = function() {
    this.next.literalEnd = this.literals.position;
  };

  this.Scanner.prototype.isHexDigit = function(ch) {
    return ('0' <= ch && ch <= '9') ||
        ('a' <= ch && ch <= 'f') ||
        ('A' <= ch && ch <= 'F');
  };

  this.Scanner.prototype.hexValue = function(ch) {
    if (this.isHexDigit(ch)) {
      return parseInt(ch, 16);
    }
    return -1;    
  };

  this.Scanner.prototype.scanHexEscape = function(ch, length) {
    var digits = [];
    var x = 0;
    for (var i = 0; i < length; i++) {
      digits[i] = this.c0;
      var d = this.hexValue(this.c0);
      if (d < 0) {
        // According to ECMA-262, 3rd, 7.8.4, page 18, these hex escapes
        // should be illegal, but other JS VMs just return the
        // non-escaped version of the original character.

        // Push back digits read, except the last one (in c0_).

        for (var j = i - 1; j >= 0; j--) {
          this.pushBack(digits[j]);
        }

        return ch;
      }
      x = x * 16 + d;
      this.advance();
    }
    return x;
  };

// Octal escapes of the forms '\0xx' and '\xxx' are not a part of
// ECMA-262. Other JS VMs support them.
  this.Scanner.prototype.scanOctalEscape = function(ch, length) {
    var x = this.hexValue(ch);
    for (var i = 0; i < length; i++) {
      if (this.c0 < "0" || this.c0 > "7") { break; }
      var d = this.hexValue(this.c0);
      var nx = x * 8 + d;
      if (nx > 256) { break; }
      x = nx;
      this.advance();
    }
  };

  this.Scanner.prototype.scanEscape = function() {
    var ch = this.c0;
    this.advance();

    // Skip escaped newlines.
    if (this.isLineTerminator(this.c0)) {
      // Allow CR+LF newlines in multiline string literals.
      if (this.ch == "\r" && this.c0 == "\n") { this.advance(); }
      // Allow LF+CR newlines in multiline string literals.
      if (this.ch == "\n" && this.c0 == "\r") { this.advance(); }
      return;
    }

    switch(ch) {
      case '\'':  // fall through
      case '"' :  // fall through
      case '\\': break;
      case 'b' : ch = '\b'; break;
      case 'f' : ch = '\f'; break;
      case 'n' : ch = '\n'; break;
      case 'r' : ch = '\r'; break;
      case 't' : ch = '\t'; break;
      case 'u' : ch = this.scanHexEscape(ch, 4); break;
      case 'v' : ch = '\v'; break;
      case 'x' : ch = this.scanHexEscape(ch, 2); break;
      case '0' :  // fall through
      case '1' :  // fall through
      case '2' :  // fall through
      case '3' :  // fall through
      case '4' :  // fall through
      case '5' :  // fall through
      case '6' :  // fall through
      case '7' : ch = this.scanOctalEscape(ch, 2); break;
    }

    // According to ECMA-262, 3rd, 7.8.4 (p 18ff) these
    // should be illegal, but they are commonly handled
    // as non-escaped characters by JS VMs.
    this.addChar(ch);    
  };

  this.Scanner.prototype.scanString = function() {
    var quote = this.c0;
    this.advance(); // consume quote

    this.startLiteral();
    while (this.c0 != quote && this.c0 != -1 && !this.isLineTerminator(this.c0)) {
      var ch = this.c0;
      this.advance();
      if (ch == "\\") {
        if (this.c0 == -1) { return that.$T("ILLEGAL"); }
        this.scanEscape();
      } else {
        this.addChar(ch);
      }
    }
    if (this.c0 != quote) {
      return that.$T("ILLEGAL");
    }
    this.terminateLiteral();
    this.advance(); // consume quote
    return that.$T("STRING");
  };

  this.Scanner.prototype.scanDecimalDigits = function() {
    while (this.isDecimalDigit(this.c0)) {
      this.addCharAdvance();
    }
  };

  this.Scanner.prototype.scanNumber = function(seenPeriod) {
    var kind = "DECIMAL";
    this.startLiteral();
    if (seenPeriod) {
      // we have already seen a decimal point of the float
      this.addChar(".");
      this.scanDecimalDigits(); // we know we have at least one digit
    } else {
      // if the first character is '0' we must check for octals and hex
      if (this.c0 == "0") {
        this.addCharAdvance();

        // either 0, 0exxx, 0Exxx, 0.xxx, an octal number, or a hex number
        if (this.c0 == "x" || this.c0 == "X") {
          // hex number
          kind = "HEX";
          this.addCharAdvance();
          if (!this.isHexDigit(this.c0)) {
            // we must have at least one hex digit after 'x'/'X'
            return that.$T("ILLEGAL");
          }
          while (this.isHexDigit(this.c0)) {
            this.addCharAdvance();
          }
        } else if ("0" <= this.c0 && this.c0 <= "7") {
          // (possible) octal number
          kind = "OCTAL";
          while (true) {
            if (this.c0 == '8' || this.c0 == '9') {
              kind = "DECIMAL";
              break;
            }
            if (this.c0 < '0' || this.c0 > '7') {
              break;
            }
            this.addCharAdvance();
          }
        }
      }
      // Parse decimal digits and allow trailing fractional part.
      if (kind == "DECIMAL") {
        this.scanDecimalDigits(); // optional
        if (this.c0 == '.') {
          this.addCharAdvance();
          this.scanDecimalDigits(); // optional
        }
      }
    }

    // scan exponent, if any
    if (this.c0 == 'e' || this.c0 == 'E') {
      if (kind == "OCTAL") { return that.$T("ILLEGAL"); }
      // scan exponent
      this.addCharAdvance();
      if (this.c0 == '+' || this.c0 == '-') {
        this.addCharAdvance();
      }
      if (!this.isDecimalDigit(this.c0)) {
        // we must have at least one decimal digit after 'e'/'E'
        return that.$T("ILLEGAL");
      }
      this.scanDecimalDigits();
    }
    this.terminateLiteral();

    // The source character immediately following a numeric literal must
    // not be an identifier start or a decimal digit; see ECMA-262
    // section 7.8.3, page 17 (note that we read only one decimal digit
    // if the value is 0).
    if (this.isDecimalDigit(this.c0) || this.isIdentifierStart(this.c0)) {
      return that.$T("ILLEGAL");
    }
    return that.$T("NUMBER");
  };

  this.Scanner.prototype.selectOne = function(token) {
    this.advance();
    return token; 
  };

  this.Scanner.prototype.advance = function() {
    this.c0 = this.stream.advance();
  };

  this.Scanner.prototype.pushBack = function(ch) {
    this.stream.pushBack(ch);
    this.c0 = ch;
  };

  this.Scanner.prototype.addChar = function(ch) {
    this.literals.addChar(ch);
  };

  this.Scanner.prototype.addCharAdvance = function() {
    this.addChar(this.c0);
    this.advance();
  };

  this.Scanner.prototype.scanToken = function() {
    switch (this.c0) {
      // strings
      case '"':
      case "'":
        return this.scanString();
      case "<":
        // < <= << <<= <!--
        this.advance();
        if (this.c0 == '=') { return this.selectOne(that.$T("LTE")); }
        if (this.c0 == '<') { return this.select('=', that.$T("ASSIGN_SHL"), that.$T("SHL")); }
        if (this.c0 == "!") { return this.scanHtmlComment(); }
        return that.$T("LT");
      case ">":
        // > >= >> >>= >>> >>>=
        this.advance();
        if (this.c0 == "=") { return this.selectOne(that.$T("GTE")); }
        if (this.c0 == ">") {
          // >> >>= >>> >>>=
          this.advance();
          if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_SAR")); }
          if (this.c0 == ">") { return this.select("=", that.$T("ASSIGN_SHR"), that.$T("SHR")); }
          return that.$T("SAR");
        }
        return that.$T("GT");
      case "=":
        // = == ===
        this.advance();
        if (this.c0 == "=") { return this.select("=", that.$T("EQ_STRICT"), that.$T("EQ")); }
        return that.$T("ASSIGN");
      case "!":
        // ! != !==
        this.advance();
        if (this.c0 == "=") { return this.select("=", that.$T("NE_STRICT"), that.$T("NE")); }
        return that.$T("NOT");
      case "+":
        // + ++ +=
        this.advance();
        if (this.c0 == "+") { return this.selectOne(that.$T("INC")); }
        if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_ADD")); }
        return that.$T("ADD");
      case "-":
        // - -- -=
        this.advance();
        if (this.c0 == "-") { return this.selectOne(that.$T("DEC")); }
        if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_SUB")); }
        return that.$T("SUB");
      case "*":
        // * *=
        return this.select("=", that.$T("ASSIGN_MUL"), that.$T("MUL"));
      case "%":
        // % %=
        return this.select("=", that.$T("ASSIGN_MOD"), that.$T("MOD"));
      case "/":
        // /  // /* /=
        this.advance();
        if (this.c0 == "/") { return this.skipSingleLineComment(); }
        if (this.c0 == "*") { return this.skipMultiLineComment(); }
        if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_DIV")); }
        return that.$T("DIV");
      case "&":
        // & && &=
        this.advance();
        if (this.c0 == "&") { return this.selectOne(that.$T("AND")); }
        if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_BIT_AND")); }
        return that.$T("BIT_AND");
      case "|":
        // | || |=
        this.advance();
        if (this.c0 == "|") { return this.selectOne(that.$T("OR")); }
        if (this.c0 == "=") { return this.selectOne(that.$T("ASSIGN_BIT_OR")); }
        return that.$T("BIT_OR");
      case "^":
        // ^ ^=
        return this.select("=", that.$T("ASSIGN_BIT_XOR"), that.$T("BIT_XOR"));
      case ".":
        // . Number
        this.advance();
        if (this.isDecimalDigit(this.c0)) { return this.scanNumber(true); }
        return that.$T("PERIOD");
      case ':':
        return this.selectOne(that.$T("COLON"));

      case ';':
        return this.selectOne(that.$T("SEMICOLON"));

      case ',':
        return this.selectOne(that.$T("COMMA"));

      case '(':
        return this.selectOne(that.$T("LPAREN"));

      case ')':
        return this.selectOne(that.$T("RPAREN"));

      case '[':
        return this.selectOne(that.$T("LBRACK"));

      case ']':
        return this.selectOne(that.$T("RBRACK"));

      case '{':
        return this.selectOne(that.$T("LBRACE"));

      case '}':
        return this.selectOne(that.$T("RBRACE"));

      case '?':
        return this.selectOne(that.$T("CONDITIONAL"));

      case '~':
        return this.selectOne(that.$T("BIT_NOT"));

      default:
        if (this.isIdentifierStart(this.c0)) { return this.scanIdentifier(); }
        if (this.isDecimalDigit(this.c0)) { return this.scanNumber(false); }
        if (this.c0 == -1) { return that.$T("EOS"); }
        return that.$T("ILLEGAL");
    }
  };

  this.Scanner.prototype.isIdentifierStart = function(ch) {
    switch (ch) {
      case "$":
      case "_":
      case "\\": return true;
      default: return this.isLetter(ch);
    }
  };

  this.Scanner.prototype.isIdentifierPart = function(ch) {
    return this.isIdentifierStart(ch) || this.isDecimalDigit(ch) ||
    // NOTE: we do not support Unicode combining marks.
        /* this.isCombiningMark(ch) || */ this.isConnectorPunctuation(ch);
  };

  this.Scanner.prototype.isLetter = function(ch) {
    // NOTE: it does not work with non-Latin chars. There's no
    // easy way to support non-Latin identifiers since javascript regular expressions
    // do not support \L escape secuences.
    // TODO: support non-Latin identifiers.
    return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z';
  };

  this.Scanner.prototype.scanIdentifierUnicodeEscape = function() {
    this.advance();
    if (this.c0 != "u") { return "\uFFFD"; } // Unicode bad char
    this.advance();
    var ch = this.scanHexEscape('u', 4);
    // We do not allow a unicode escape sequence to start another
    // unicode escape sequence.
    if (ch == "\\") { return "\uFFFD"; } // Unicode bad char
    return ch;
  };

  this.Scanner.prototype.scanIdentifier = function() {
    var hasEscapes = false;

    this.startLiteral();

    var parser = this;
    var handleChar = function(predicate) {
      if (parser.c0 == "\\") {
        hasEscapes = true;
        var ch = parser.scanIdentifierUnicodeEscape();
        // Only allow legal identifier start characters.
        if (!predicate(ch)) { return that.$T("ILLEGAL"); }
        parser.addChar(ch);
      } else {
        parser.addCharAdvance();
      }
    };
    // Scan identifier start character.
    handleChar(this.isIdentifierStart);

    // Scan the rest of the identifier characters.
    while (this.isIdentifierPart(this.c0)) {
      handleChar(this.isIdentifierPart);
    }
    this.terminateLiteral();

    // We don't have any 1-letter keywords (this is probably a common case).
    if (this.next.literalEnd - this.next.literalPosition == 1) {
      return that.$T("IDENTIFIER");
    }
    // If the identifier contains unicode escapes, it must not be
    // resolved to a keyword.
    if (hasEscapes) {
      return that.$T("IDENTIFIER");
    }

    return that.getTokenByString(this.getNextLiteralString());
  };
};
